home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Source Code / Libraries / Sherlock 2.0 / Mac v2.0 docs / Text Docs / Mac DevLIb .txt < prev    next >
Text File  |  1996-04-05  |  20KB  |  504 lines

  1. The DevLib Development Library
  2.  
  3. This Chapter describes the various files in the folder DevLibSrc.  The development library, 
  4. DevLib, provides a set of common functions used by a C compiler, assembler, linker and 
  5. debugger, as well as SPP, SDEL and SDIF.  These routines are not specific to the Macintosh: they 
  6. may be of interest to C programmers using a wide variety of environments.
  7.  
  8.  
  9. DevLib defines a set of classes.  Each class is represented by a .h file and one or two .c files.  The 
  10. .c files represent the “base” methods of each class.  When a member (method or data) of a class 
  11. must be over-ridden by an application, that member is not defined in DevLibSrc.  Rather, it is 
  12. defined in an application-specific, say XXyy.c file.  That is, XX stands for the name of your 
  13. application, while yy stands for the particular class.
  14.  
  15.  
  16. The externally visible public members (functions and data) of any class are exactly those functions 
  17. and data declared in the .h file for the class.  In particular, each .h file contains a function prototype 
  18. for each public method.
  19.  
  20.  
  21. The library consists of the following classes and auxiliary headers.  Classes are identified by the 
  22. name of its .c file.  The .c file typically contains more complete documentation than the 
  23. corresponding .h file.
  24.  
  25.  
  26. The LIBmem class is particularly interesting.  It describes an optimal method of allocating memory 
  27. based on so-called lifetime classes.  Also, the LIBobj class describes replacements for malloc and 
  28. free.  These classes complement each other; lifetime classes ultimately call malloc to get blocks of 
  29. memory.
  30.  
  31.  
  32.  
  33. LIBcmnd.c
  34.  
  35. This class parses a disk parameter file into a set of argv arguments.  I use parameter files 
  36. containing hundreds of Sherlock arguments.  Not only do the arguments control the program, they 
  37. provide a record of what I am working on.
  38.  
  39.  
  40. The init_args function is most important routine of this class.  init_args reads a text file, parses it 
  41. into separate arguments, allocates an argv vector, fills in the argv vector with pointers to the 
  42. arguments, and computes the value of argc.
  43.  
  44. LIBcvt.c
  45.  
  46. This class converts various kinds of variables into strings; it is a type-safe replacement for sprintf.  
  47. This class guards against overwriting the sprintf buffer.  Most routines in this class require the 
  48. caller to supply a buffer into which the resulting string is put.  The routines checks that the string 
  49. does not overflow the buffer.  These checks are not foolproof because they rely on the stated size 
  50. of the buffer passed by the caller
  51.  
  52.  
  53. This class is used mainly by the stream output routines in LIBes.c.  Once in a while, however, 
  54. these routines are useful in their own right.
  55.  
  56.  
  57. The compile-time constant CVT_USE_SPRINTF allows this file to avoid all calls to sprintf.  This 
  58. was useful when bootstrapping a C compiler.
  59.  
  60.  
  61.  
  62. LIBend.c and XXend.c
  63.  
  64. This class handles end-of-program processing.  Each application must define its own 
  65. end_close_all method, in an application-specific file, XXend.c.
  66.  
  67.  
  68.  
  69. LIBenv.c
  70.  
  71. This class reports on various system-dependent quantities, such as heap statistics that may be kept 
  72. by the operating system itself.  At present, the code reports on the status of Macintosh heap zones.  
  73. This class is not defined for Power Macs.
  74.  
  75.  
  76.  
  77. The files LIBes.c and XXes.c
  78.  
  79. This class contains many stream output routines that are type safe replacements for fprintf.  SPP, 
  80. SDEL and SDIF contain many examples of how to use these routines.
  81.  
  82.  
  83. All output from these routines eventually passes through the es routine, which is not defined in 
  84. LIBes.  Instead, each application is supposed to “override” es by providing its own version of es, 
  85. in an application-specific file, XXes.c. On the Macintosh es calls log_sout in LIBlog.c; on other 
  86. machines es could call fprintf(stderr, “%s”, s);
  87.  
  88. Each application must also provide definitions for the following routines:  ecnl, ecnls, ecblanks, 
  89. es_assert_failed and es_internal_err.  See the file SPPes.c for an example of the per-application 
  90. definitions of these routines.
  91.  
  92.  
  93. The ecnl routine deserves special mention.  It outputs a conditional newline.  That is, it outputs a 
  94. newline only if the last character output was not a newline.  Conditional newlines make it easy to 
  95. generate correct spacing without requiring strict coding conventions.
  96.  
  97.  
  98. Short function names are very important here.  In particular, es, enl, ecnl and eint must have short 
  99. names to be convenient to use.  In effect, these names are all variants of the C++   <<   operator.
  100.  
  101.  
  102.  
  103. LIBio.c
  104.  
  105. This class provides slightly enhanced open, close, read and write routines.  This file was more 
  106. useful before the ANSI Standard defined the library rigorously, but it is still sometimes useful to 
  107. isolate system dependencies.
  108.  
  109.  
  110.  
  111. LIBlib.c
  112.  
  113. This file is not a class.  Rather, it specifies application-specific information to the library.  Make a 
  114. separate copy of this file with a separate name, say XXlib.c,  for each application.  Do not use 
  115. LIBlib.c.  It is only a template.
  116.  
  117.  
  118. This is a simple, yet crucial file.  Using variables to contain constant information is an Aha that 
  119. makes it possible for different programs to share the same source code.  In effect, the linker injects 
  120. these constants into the source code of the library.
  121.  
  122.  
  123.  
  124. LIBlist.h
  125.  
  126. This class consists only of macros;  there is no corresponding .c file.  This file contains type-safe 
  127. macros for list processing.  Using functions instead of macros would require type-defeating casts 
  128. to void *.  Macros can avoid such casts.  Think of these macros as a kind of C++ template. 
  129.  
  130.  
  131. Lifetime memory allocation reduces the need for these macros.  (See LIBmem.h for a discussion 
  132. of lifetime allocation.)  Elements of lists almost always have the same lifetime, so it is seldom 
  133. necessary to free list elements explicitly.  With lifetime allocation, the list head is freed at the same 
  134. time all the nodes are freed.  Most list processing simply disappears!
  135.  
  136. LIBlog.c
  137.  
  138. This class handles output to a log file and window.  Usually all output from es is directed here.  
  139. Distinguishing this class from the class that prints the error stream simplified the code greatly.
  140.  
  141.  
  142. By convention, Sherlock opens a log file when it sees a Sherlock argument of the form
  143.  
  144.  
  145.     ++>>file_name.
  146.  
  147.  
  148. Only one log file can be open at a time.
  149.  
  150.  
  151.  
  152. LIBmem.c, LIBmem.h, XXmem.c and XXmem.h
  153.  
  154. This class implement what I call the lifetime method of storage allocation.  I have used this method 
  155. of storage allocation in an ANSI C compiler that I recently completed.  It is not used in SPP, SDIF 
  156. or SDEL.  I describe it here because it is an important part of DevLib.
  157.  
  158.  
  159. Defining lifetimes
  160.  
  161. Let us define the lifetime of a heap object to be the time (point in the code) at which the object is 
  162. freed.  This definition is deceptively simple.  Note the following:
  163.  
  164.  
  165. • The lifetime of an object does not depend on when the object is created, only when it is freed.
  166.  
  167. • The type of an object does not influence its lifetime.  Objects of different types and sizes may 
  168. have the same lifetime.
  169.  
  170. • The lifetime of an object is determined when the object is created.  It does not change.
  171.  
  172.  
  173. Implementing lifetimes
  174.  
  175. Arbitrarily many lifetimes may exist in a program.   A global variable points at the head of the list 
  176. of lifetimes.  Position within this list has no significance.  A dynamically allocated node describes 
  177. each lifetime.  Each node contains the following information:
  178.  
  179.  
  180. • The name of the lifetime.  This name is used to create column headers in dumps of statistics.
  181.  
  182. • A pointer to the head of a list of blocks comprising the memory used by the lifetime.  The first 
  183. block on the list is the current block.  Memory is always allocated from the current block.  A new 
  184. block is added to the list whenever the current block does not contain enough free memory to 
  185. handle a request for more memory.
  186.  
  187. • The number of free bytes in the current block and a pointer to the first free byte.
  188.  
  189. • A pointer to the head of a list of per-type statistics nodes for this lifetime.  Each statistic node 
  190. describes the statistics for objects of the same type in this particular lifetime.  Statistics nodes 
  191. contain statistics and a type name used in dumps.
  192.  
  193. • A pointer to the next lifetime on the global list of all lifetimes.
  194.  
  195.  
  196. Allocating memory
  197.  
  198. I allocate memory use a variety of macros.  The file LIBmem.h contains the basic macros.  The 
  199. new_macro allocates enough bytes to hold an object whose pointer is given.  For example:
  200.  
  201.  
  202.     my_type * p;
  203.  
  204.     new_macro(p, perm_life, perm_type_stat);
  205.  
  206.  
  207. perm_life is a pointer to a lifetime node and perm_type_stat a pointer to a statistics node attached to 
  208. perm_life.
  209.  
  210.  
  211. For safety, I would define an abbreviation macro defined in an application-defined header, say 
  212. XXmem.h.  A typical abbreviation macro would be:
  213.  
  214.  
  215.     #define new_perm_my_type_macro(p) \
  216.  
  217.         new_macro(p, perm_life, perm_my_type_stat)
  218.  
  219.  
  220. This macro ensures that the proper statistic node is used with the proper lifetime. In other words, 
  221. abbreviation macros represent associations, in this example the association of perm_my_type_stats 
  222. with perm_life.
  223.  
  224.  
  225. The new_macro is defined as follows in LIBmem.h:
  226.  
  227.  
  228.     #define new_macro(p, life, stat) \
  229.  
  230.         new_size_macro(p, sizeof(*p), life, stat);
  231.  
  232.  
  233. new_macro computes the number of bytes to be allocated using sizeof(*p).  This will work 
  234. regardless of what p points to.  The compiler will complain if p is not, in fact, a pointer.  This 
  235. macro is type safe and bullet-proof.
  236.  
  237.  
  238.  
  239. All the real work is done by the new_size macro.  The actual definition of this macro is a bit 
  240. complicated:  see LIBmem.h.  A simplified form of the macro is as follows:
  241.  
  242.  
  243.     #define new_size_macro(p, size, life, stat)            \
  244.  
  245.         // update statistics for stat                    \
  246.  
  247.         if (size > life -> mem_avail) {                \
  248.  
  249.             p = mem_new_block(size_, life);            \
  250.  
  251.         }                                    \
  252.  
  253.         else {                                \
  254.  
  255.             p = life -> mem_ptr;                    \
  256.  
  257.             life -> mem_ptr   = life -> mem_ptr + size;    \
  258.  
  259.             life -> mem_avail -= size_;                \
  260.  
  261.         }                                    \
  262.  
  263.     }
  264.  
  265.  
  266. Usually, the current block will contain more than size bytes.  In that case, we execute the 
  267. following instructions:
  268.  
  269.  
  270.     p = life -> mem_ptr;
  271.  
  272.     life -> mem_ptr    = life -> mem_ptr + size;
  273.  
  274.     life -> mem_avail -= size;
  275.  
  276.  
  277. Clearly, it's not possible to allocate memory more quickly than these 3 instructions..
  278.  
  279.  
  280. When the current block does not contain enough free memory for the requested item, the macro 
  281. call mem_new_block to get a new block of memory.  mem_new_block is defined in LIBmem.c.  
  282. It sets life -> mem_ptr and life -> mem_avail to describe the newly allocated block.  
  283. mem_new_block will usually allocate a standard-sized block (like 1024 bytes), but can allocate an 
  284. arbitrarily large block if the requested number of bytes would not fit in a standard-sized block.
  285.  
  286.  
  287. new_size_macro is the fastest possible way to allocate memory.  Indeed, it doesn't matter how fast 
  288. mem_new_block is!  The reason is simple: the current block will usually be big enough to contain 
  289. the next item, assuming that most allocated items are small compared to the standard block size.  
  290. We can arbitrarily decrease the cost of calling mem_new_block by increasing the size of the 
  291. standard block.
  292.  
  293.  
  294. So much for allocating memory.  Deallocating memory is even more efficient.  As a consequence 
  295. of the definition of a lifetime, all items with the same lifetime are deallocated at once.  Deallocating 
  296. a lifetime is trivial: we simply free all of the blocks of the lifetime.
  297.  
  298.  
  299.  
  300. LIBobj.c and LIBobj.h
  301.  
  302. This class provides more robust versions of calloc and free.  This class has a long history: the 
  303. present version if much easier to use and more powerful than previous versions.  This class is 
  304. probably more powerful than some of the malloc replacements being sold today.
  305.  
  306.  
  307. Access this class using the obj_new_macro or obj_free_macro, defined in LIBobj.h.  A third 
  308. macro, obj_new_var_tag_macro is much less often used, though it is sometimes essential.
  309.  
  310.  
  311. The compile-time constant PRODUCTION controls these macros.  When PRODUCTION is 
  312. #defined, the macros expand essentially to calloc and free.  Otherwise, the macros expand to 
  313. routines that provide memory protection and statistics gathering capabilities.
  314.  
  315.  
  316. These routines use a string as a type name without sacrificing efficiency.  I call this the Sherlock 
  317. trick.  How does this work?  Let’s look at the definition of the debugging version of 
  318. obj_new_macro:
  319.  
  320.  
  321.     #define obj_new_macro(the_mem, size, tag) {        \
  322.  
  323.         static type_des * tp_ = NULL;                \
  324.  
  325.         the_mem = obj_new_db(size, tag, &tp_);        \
  326.  
  327.     }
  328.  
  329.  
  330. The first time a particular instance of this macro is called, the static variable tp_ is NULL, so 
  331. obj_new_db searches a hashed list of all types it knows about for the tag.  If it is found, it sets tp_ 
  332. to point to the type_des describing the type.  Otherwise it creates a type_des describing the new 
  333. type and points tp_ to it.  The second time a particular instance of this macro is called, tp_ has 
  334. already been set, and obj_new_db can access the type_des directly without any searching at all.
  335.  
  336.  
  337. Notice: it is never necessary to initialize a type_des;  obj_new_macro does it automatically.  The 
  338. tag string is enough to initialize the new type.  In effect, the tag is a type.  So to create a new type, 
  339. just call obj_new_macro with a new string argument.  The list of tag names is searched only once 
  340. per instance of the macro, so the time overhead is not noticeable.
  341.  
  342.  
  343. Usually the tag argument is a normal, statically allocated string.  The Sherlock trick won’t work if 
  344. the tag argument is a variable because tp_ must then be recalculated on each call.  In those rare 
  345. cases, use obj_new_var_tag_macro.  This macro essentially sets tp_ every time it is called.
  346.  
  347.  
  348. This class can produce a formatted report of statistics for all objects, similar to the report provided 
  349. by the LIBmem class.  LIBmem allocates memory in blocks, so only those blocks will show up in 
  350. the report created by LIBobj.  
  351.  
  352.  
  353. When PRODUCTION is not defined, all memory is preceded and followed by fill bytes that are 
  354. filled with a known pattern.   Most memory overwrites can easily be found by checking these fill 
  355. bytes.
  356.  
  357.  
  358. When the ++obj_v Sherlock trace is enabled all fill bytes are checked any time a new object is 
  359. allocated or freed.  Obviously, this takes a lot of time, and it is very useful in finding memory 
  360. problems.
  361.  
  362.  
  363. When the ++obj_watch Sherlock trace is enabled all fill bytes are put on a Sherlock watch list.  
  364. (I’ll discuss watch lists below).  This watch list is checked whenever a Sherlock macro is 
  365. executed, so all fill bytes are checked whenever a Sherlock macro is executed.  This option takes 
  366. even more time than the ++obj_v option, but finds memory problems quickly.
  367.  
  368.  
  369. Regardless of the settings of ++obj_watch and ++obj_v, the fill bytes of an object are checked 
  370. when it is freed.
  371.  
  372.  
  373.  
  374. LIBos.c
  375.  
  376. This class provides an output stream.  There are two kinds of routines in this class.  The out_bytes 
  377. routine provides non-text output.  All other routines provide text output.  All text output is 
  378. funneled through the os routine, so redirecting output is feasible and simple.
  379.  
  380.  
  381.  
  382. Sherlock (sl_xxx.c and sl.h)
  383.  
  384. Sherlock is a way of enabling and disabling code at run time.  Sherlock consists of a large set of 
  385. macros such as TRACE.  TRACE(a,b) executes the code b if the string a has been enabled from 
  386. the command line.  For example,
  387.  
  388.     
  389.  
  390.     TRACE("obj_v", obj_check_all());
  391.  
  392.  
  393. This macro executes obj_check_all if ++obj_v has been selected on the command line.  If the 
  394. compile-time constant called SHERLOCK is not #defined, all Sherlock macros expand to do-
  395. nothing code.
  396.  
  397. The second parameter  to the TRACE macro can be any sequence of C source statements.  The 
  398. following is perfectly valid:
  399.  
  400.  
  401.     TRACE("xyz",
  402.  
  403.         {
  404.  
  405.             int i;
  406.  
  407.             for(i = 0; i < limit; i++)
  408.  
  409.                 fprintf(stderr, "array[%d]=%d\n", i, array[i]);
  410.  
  411.         }
  412.  
  413.     );
  414.  
  415.  
  416. In particular, the commas in the call to fprintf do not delimit macro parameters because they are 
  417. inside parentheses.  About the only thing you can't put inside a Sherlock macro is an initializer 
  418. containing commas.
  419.  
  420.  
  421. The Sherlock trick is important here.  Without it, we would have to declare and initialize a variable 
  422. for every different tag, xyz in the example above.  This would be intolerable.  The Sherlock trick 
  423. allows us to dispense with tracing variables!  My programs have hundreds of tags that can be 
  424. enabled or disabled independently.  None of these tags require any initialization on my part.  The 
  425. only initialization is a single call to parse the Sherlock arguments and remove them from the argv 
  426. list so they don’t interfere with the program’s usual arguments.
  427.  
  428.  
  429. My preferred style of using Sherlock has changed a bit since Sherlock was first released:
  430.  
  431. • I no longer use the RETURN_xxx family of macros.  These turned out to be too clumsy.  A 
  432. typical contains a single exit point, immediately preceded by a STATX or TRACEPX macro.  I 
  433. prefer to use goto done to indicate a return from a function.  This complicates some functions, but 
  434. not much.
  435.  
  436. • I often use STATB as the first Sherlock macro.  That way, the timing statistics of the routines are 
  437. not influenced by the tracing that would be produced by TRACEPB.
  438.  
  439.  
  440. Watch lists are new.  The SL_WATCH macro places a node describing an area of memory and its 
  441. contents on a list.  This list is checked whenever a Sherlock macro is executed.  I place Sherlock 
  442. macros at the start and end of almost all my functions, so this watch list is examined at the start 
  443. and end of each function.  We can catch memory being trashed within the function that trashed it, 
  444. yet the overhead of examining the watch list is much less than if it were examined after every 
  445. instruction.  By the way, only a few dozen lines of code in sl_macro.c are needed to implement 
  446. watch lists.
  447.  
  448. LIBtypes.h
  449.  
  450. This file contains the typedefs for types that are shared among the files of DevLib.  The actual 
  451. definitions of the structs need not appear in this header file!   This has the following advantages:
  452.  
  453.  
  454. • It allows struct A in a header file H to refer to struct B even though the definition of struct B does 
  455. not appear in H.
  456.  
  457. • Include files can be included in any order because all necessary typedefs are in LIBtypes.h.
  458.  
  459. • Typedefs change rarely, and LIBtypes.h needs change only if a new global type is created.
  460.  
  461. • Include files can correspond closely to C++ classes.  That is, the declarations of derived classes 
  462. need not be contained in the header that contains the declaration of the base class.  Header files can 
  463. describe a single class, whether or not that class is derived from another class.
  464.  
  465.  
  466. There are a few drawbacks to using LIBtypes.h, none of which are significant for small and 
  467. medium sized programs.
  468.  
  469.  
  470. • The typedefs in LIBtypes.h are global, even though they may be used only in one or two files.
  471.  
  472. • All library files depend on LIBtypes.h, so they are a bit less self contained.
  473.  
  474.  
  475.  
  476. Possible Improvements
  477.  
  478. I keep finding new uses for the Sherlock trick.  One possibility would be to create stat nodes 
  479. automatically by defining the macro MEM_UPDATE_STATS in LIBmem.h as follows:
  480.  
  481.         
  482.  
  483.     {                                \
  484.  
  485.         static mem_stat_node * h = NULL;        \
  486.  
  487.         if (h == NULL) h = mem_init_stats(tag);    \
  488.  
  489.         // update h as usual                \
  490.  
  491.     }
  492.  
  493.         
  494.  
  495. This would eliminate the need to define and initialize statistics variable.   In particular, you 
  496. wouldn’t need to change the application-specific version of LIBmem.h to create a new kind of 
  497. node.  Very convenient.
  498.  
  499.  
  500. This is a bit slower than the old scheme.  Ignoring (as we should) the time to execute 
  501. mem_init_stats once, the test (h== NULL) almost doubles the time to execute the macro.  
  502. However, statistics are not typically kept in production code, so the speed penalty is usually 
  503. negligible.
  504.